﻿using System.Collections.Generic;
using System.Reflection;
using System.Text;

using Utilities;

namespace Utilities
{
    public static class JsonSerializer
    {
        /// <summary>
        /// Serializes an object as a JSON string
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public static string Serialize(object obj, bool omitNulls = false, bool format = false)
        {
            StringBuilder builder = new StringBuilder();
            List<int> lastCommas = new List<int>();

            ObjectTraversal traversal = new ObjectTraversal
            {
                OnNullObject = (name) =>
                {
                    if (omitNulls)
                    {
                        return;
                    }

                    if (name != null)
                    {
                        builder.Append(JsonConverter.Convert(name));
                        builder.Append(":null");

                        SetLastComma(lastCommas, builder);
                        builder.Append(",");
                    }
                },
                OnBeginCollection = (name, o, p) =>
                {
                    if (p != null) // We might serialize a collection without parent
                    {
                        PropertyInfo pi = p.GetType().GetProperty(name);

                        if (pi.IsDefined(typeof(JsonIgnoreAttribute), false))
                        {
                            return false;
                        }

                        SetLastComma(lastCommas, builder); // For the properties in the object

                        if (name != null)
                        {
                            builder.Append(JsonConverter.Convert(name));
                            builder.Append(":");
                        }
                    }

                    AddLastComma(lastCommas); // For the items

                    builder.Append("[");
                    return true;
                },
                OnEndCollection = (o, p) =>
                {
                    RemoveLastComma(lastCommas, builder); // Remove the last comma for the items

                    builder.Append("]");

                    if (SetLastComma(lastCommas, builder) == true) // For the items in the collection
                    {
                        builder.Append(",");
                    }
                },
                OnBeginObject = (name, o, p, isItem) =>
                {

                    if (p != null)
                    {
                        PropertyInfo pi = p.GetType().GetProperty(name);

                        if (pi.IsDefined(typeof(JsonIgnoreAttribute), false))
                        {
                            return false;
                        }
                    }

                    AddLastComma(lastCommas); // For the properties

                    if (p != null && !isItem)
                    {
                        builder.Append(JsonConverter.Convert(name));
                        builder.Append(":");
                    }

                    builder.Append("{");
                    return true;
                },
                OnEndObject = (o, p, isItem) =>
                {
                    RemoveLastComma(lastCommas, builder); // Remove the last comma for the properties

                    builder.Append("}");

                    if (SetLastComma(lastCommas, builder) == true) // For the properties of the object
                    {
                        builder.Append(",");
                    }
                },
                OnPrimitive = (name, o, p, isItem) =>
                {
                    PropertyInfo pi = p.GetType().GetProperty(name);

                    if (pi.IsDefined(typeof(JsonIgnoreAttribute), false))
                    {
                        return;
                    }

                    builder.Append(JsonConverter.Convert(name));
                    builder.Append(":");
                    builder.Append(JsonConverter.Convert(o));

                    SetLastComma(lastCommas, builder); // For the properties in the object
                    builder.Append(",");
                }
            };

            traversal.Traverse(obj);

            return builder.ToString();
        }

        /// <summary>
        /// Extension method to serialize any object
        /// </summary>
        /// <param name="o"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public static string ToJson(this object o, bool omitNulls = false, bool format = false)
        {
            return Serialize(o, omitNulls: omitNulls, format: format);
        }

        #region Last comma helpers

        private static void AddLastComma(List<int> lastCommas)
        {
            lastCommas.Add(-1);
        }

        private static bool SetLastComma(List<int> lastCommas, StringBuilder builder)
        {
            if (lastCommas.Count == 0)
            {
                return false;
            }

            int lastIndex = lastCommas.Count - 1;
            lastCommas[lastIndex] = builder.Length;

            return true;
        }

        private static void RemoveLastComma(List<int> lastCommas, StringBuilder builder)
        {
            if (lastCommas.Count == 0) // Nothing to remove
            {
                return;
            }

            int lastIndex = lastCommas.Count - 1;
            int lastComma = lastCommas[lastIndex];

            if (lastComma > -1)
            {
                builder.Remove(lastComma, 1);
            }

            lastCommas.RemoveAt(lastIndex);
        } 

        #endregion
    }
}
